using System.Linq;
using System.Text;
using WalletWasabi.Helpers;
using WalletWasabi.Tor.Socks5.Models.Bases;
using WalletWasabi.Tor.Socks5.Models.Fields.OctetFields;

namespace WalletWasabi.Tor.Socks5.Models.Fields.ByteArrayFields;

public class AddrField : ByteArraySerializableBase
{
	#region Constructors

	public AddrField(byte[] bytes)
	{
		Bytes = Guard.NotNullOrEmpty(nameof(bytes), bytes);

		AtypField atyp;
		if (bytes.First() == bytes.Length - 1 && bytes.Length != 4)
		{
			atyp = AtypField.DomainName;
		}
		else if (bytes.Length == 4)
		{
			atyp = AtypField.IPv4;
		}
		else
		{
			throw new FormatException($"Could not read IPv4 or domain name from {nameof(bytes)}. Value: {bytes}.");
		}

		Atyp = atyp;
	}

	/// <param name="dstAddr">Domain or IPv4</param>
	public AddrField(string dstAddr)
	{
		dstAddr = Guard.NotNullOrEmptyOrWhitespace(nameof(dstAddr), dstAddr, true);

		var atyp = new AtypField(dstAddr);

		Atyp = atyp;

		byte[] bytes;
		if (atyp == AtypField.DomainName)
		{
			// https://www.ietf.org/rfc/rfc1928.txt
			// the address field contains a fully-qualified domain name. The first
			// octet of the address field contains the number of octets of name that
			// follow, there is no terminating NUL octet.
			var domainBytes = Encoding.ASCII.GetBytes(dstAddr); // Tor only knows ASCII, UTF8 results in general SOCKS server failure
			var numberOfOctets = domainBytes.Length;
			if (numberOfOctets > 255)
			{
				throw new FormatException($"{nameof(dstAddr)} can be maximum 255 octets. Actual: {numberOfOctets} octets. Value: {dstAddr}.");
			}

			bytes = ByteHelpers.Combine(new byte[] { (byte)numberOfOctets }, domainBytes);
		}
		else if (atyp == AtypField.IPv4)
		{
			// the address is a version-4 IP address, with a length of 4 octets
			var parts = dstAddr.Split(".", StringSplitOptions.RemoveEmptyEntries);
			if (parts.Length != 4 || parts.Any(string.IsNullOrWhiteSpace))
			{
				throw new FormatException($"{nameof(dstAddr)} must be 4 parts. Actual: {parts.Length} parts. Value: {dstAddr}.");
			}

			bytes = new byte[4];
			for (int i = 0; i < 4; i++)
			{
				if (int.TryParse(parts[i], out int partInt))
				{
					if (partInt is < 0 or > 255)
					{
						throw new FormatException($"Every part of {nameof(dstAddr)} must be between 0 and 255. The {i}. part is invalid: {partInt}. Value of {nameof(dstAddr)}: {dstAddr}");
					}
					bytes[i] = (byte)partInt;
				}
				else
				{
					throw new FormatException($"Could not parse the {i}. part of {nameof(dstAddr)} to int. Invalid part: {partInt}. Value of {nameof(dstAddr)}: {dstAddr}.");
				}
			}
		}
		else
		{
			throw new NotSupportedException($"{nameof(atyp)} not supported. Value: {atyp}.");
		}

		Bytes = bytes;
	}

	#endregion Constructors

	#region PropertiesAndMembers

	private byte[] Bytes { get; }

	public AtypField Atyp { get; }

	public string DomainOrIPv4
	{
		get
		{
			if (Atyp == AtypField.DomainName)
			{
				return Encoding.ASCII.GetString(Bytes.Skip(1).ToArray()); // UTF8 result in general SOCKS server failure
			}

			if (Atyp != AtypField.IPv4)
			{
				throw new NotSupportedException($"{nameof(Atyp)} not supported. Value: {Atyp}.");
			}

			var values = new string[4];
			for (int i = 0; i < 4; i++)
			{
				values[i] = Bytes[i].ToString(); // it's ok ASCII here, these are always numbers
			}
			return string.Join(".", values);
		}
	}

	#endregion PropertiesAndMembers

	#region Serialization

	public override byte[] ToBytes() => Bytes;

	public override string ToString() => DomainOrIPv4;

	#endregion Serialization
}
